using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

namespace Taco.Timeline
{
    public enum ExtraPolationMode
    {
        None = 0,
        Hold = 1,
    }

    public partial class Timeline : ScriptableObject
    {
        public static int FrameRate = 60;
        public static float MinEvaluateDeltaTime
        {
            get
            {
                if (Application.isPlaying)
                    return UnityEngine.Time.deltaTime;
                return 1f / FrameRate;
            }
        }

        [SerializeReference]
        protected List<Track> m_Tracks = new List<Track>();
        public List<Track> Tracks => m_Tracks;

        public event Action OnEvaluated;
        public event Action OnRebind;
        public event Action OnDone;
        public event Action OnBindStateChanged;
        public event Action OnValueChanged;

        float m_Time;
        public float Time
        {
            get => m_Time;
            set
            {
                m_Time = value;
                OnEvaluated?.Invoke();

                if (m_Time > Duration)
                {
                    OnDone?.Invoke();
                    OnDone = null;
                }
            }
        }
        public int Frame => Mathf.RoundToInt(Time * FrameRate);

        public int MaxFrame { get; protected set; }
        public float Duration { get; protected set; }

        bool m_Binding;
        public bool Binding { get => m_Binding; protected set => m_Binding = value; }

        TimelinePlayer m_TimelinePlayer;
        public TimelinePlayer TimelinePlayer { get => m_TimelinePlayer; protected set=> m_TimelinePlayer = value; }
        public PlayableGraph PlayableGraph { get; protected set; }
        public AnimationLayerMixerPlayable RootPlayable { get; protected set; }
        public Queue<int> EmptyInputs { get; protected set; } = new Queue<int>();

        public void Init()
        {
            #region Unbind
            bool isBinding = Binding;
            TimelinePlayer timelinePlayer = TimelinePlayer;
            if (isBinding)
            {
                timelinePlayer.Dispose();
            }
            #endregion

            #region Init
            m_Tracks.ForEach(t => t.Init(this));
            MaxFrame = 0;
            foreach (var track in m_Tracks)
            {
                track.Init(this);
                if (track.MaxFrame > MaxFrame)
                    MaxFrame = track.MaxFrame;
            }
            Duration = (float)MaxFrame / FrameRate;
            OnValueChanged?.Invoke();
            #endregion

            #region Bind
            if (isBinding)
            {
                timelinePlayer.Init();
                timelinePlayer.AddTimeline(this);
            }
            #endregion
        }
        public void Bind(TimelinePlayer timelinePlayer)
        {
            Time = 0;
            TimelinePlayer = timelinePlayer;
            PlayableGraph = timelinePlayer.PlayableGraph;
            RootPlayable = timelinePlayer.RootPlayable;

            Binding = true;
            OnRebind = null;
            OnValueChanged += RebindAll;

            m_Tracks.ForEach(t => t.Bind());

            OnBindStateChanged?.Invoke();
        }
        public void Unbind()
        {
            m_Tracks.ForEach(t => t.Unbind());

            EmptyInputs.Clear();
            Binding = false;
            OnRebind = null;
            OnValueChanged -= RebindAll;

            RootPlayable = AnimationLayerMixerPlayable.Null;
            PlayableGraph = default;
            TimelinePlayer = null;

            OnBindStateChanged?.Invoke();
        }
        public void JumpTo(float targetTime)
        {
            float deltaTime = targetTime - Time;
            TimelinePlayer.AdditionalDelta = deltaTime;
        }
        public void RebindAll()
        {
            if (Binding)
            {
                OnRebind?.Invoke();
                OnRebind = null;

                m_Tracks.ForEach(t => t.Rebind());
                float currentTime = Time;
                Time = 0;
                //Evaluate(currentTime);
                TimelinePlayer.Evaluate(currentTime);
            }
        }
        public void RebindTrack(Track track)
        {
            if (Binding)
            {
                track.Rebind();
                track.TrackPlayable.SetTime(Time);
                foreach (var decorator in track.ClipDecorators)
                {
                    decorator.Handle.SetTime(Time);
                    decorator.PrepareFrame(Time);
                }
                TimelinePlayer.Evaluate(0);
            }
        }
        public void RuntimeMute(int index, bool value)
        {
            if (0 <= index && index < m_Tracks.Count)
                RuntimeMute(m_Tracks[index], value);
        }
        public void RuntimeMute(Track track, bool value)
        {
            track.RuntimeMuted = value;
            RootPlayable.SetInputWeight(track.PlayableIndex, value ? 0 : 1);
            foreach (var decorator in track.ClipDecorators)
            {
                decorator.UpdateMuteState();
            }
        }
    }

    [Serializable]
    public abstract partial class Track
    {
        public string Name;

        [SerializeField]
        protected bool m_PersistentMuted;
        public bool PersistentMuted
        {
            get => m_PersistentMuted;
            set
            {
                if (m_PersistentMuted != value)
                {
                    m_PersistentMuted = value;
                    OnMutedStateChanged?.Invoke();
                }
            }
        }

        protected bool m_RuntimeMuted;
        public bool RuntimeMuted
        {
            get => m_RuntimeMuted;
            set
            {
                if (m_RuntimeMuted != value)
                {
                    m_RuntimeMuted = value;
                    OnMutedStateChanged?.Invoke();
                }
            }
        }

        [SerializeReference]
        protected List<Clip> m_Clips = new List<Clip>();
        public List<Clip> Clips => m_Clips;

        public Action OnUpdateMix;
        public Action OnMutedStateChanged;

        public Timeline Timeline { get; protected set; }
        public int MaxFrame { get; protected set; }
        public int PlayableIndex { get; protected set; }
        public Playable TrackPlayable { get; protected set; }
        public List<TimelineClipDecorator> ClipDecorators { get; protected set; }

        public virtual void Init(Timeline timeline)
        {
            Timeline = timeline;

            MaxFrame = 0;
            foreach (var clip in m_Clips)
            {
                clip.Init(this);
                if (clip.EndFrame > MaxFrame)
                    MaxFrame = clip.EndFrame;
            }
        }
        public abstract void Bind();
        public abstract void Unbind();
        public abstract void Rebind();
    }

    [Serializable]
    public abstract class Track<T> : Track where T : TimelineClipPlayable, new()
    {
        public override void Bind()
        {
            //Create TrackPlayable
            PlayableIndex = Timeline.EmptyInputs.Dequeue();
            Timeline.RootPlayable.ConnectInput(PlayableIndex, TrackPlayable, 0, 1);
            ClipDecorators = new List<TimelineClipDecorator>();

            if (m_PersistentMuted)
                return;

            for (int i = 0; i < Clips.Count; i++)
            {
                var timelineClipDecorator = TimelineClipDecorator.Create(TrackPlayable, this, i);
                var timelineClipPlayable = TimelineClipPlayable.Create<T>(Clips[i], Timeline);
                timelineClipDecorator.Connect(timelineClipPlayable);
                Clips[i].Bind(timelineClipPlayable);
                ClipDecorators.Add(timelineClipDecorator);
            }
        }
        public override void Unbind()
        {
            if (TrackPlayable.IsValid())
            {
                Clips.ForEach(c => c.Unbind());
                ClipDecorators.ForEach(c => c.Destroy());
                Timeline.EmptyInputs.Enqueue(PlayableIndex);
                Timeline.RootPlayable.DisconnectInput(PlayableIndex);
                TrackPlayable.Destroy();
                TrackPlayable = Playable.Null;
            }
            RuntimeMuted = false;
        }
        public override void Rebind()
        {
            Unbind();
            Bind();
        }
    }

    [Serializable]
    public abstract partial class Clip
    {

        [ShowInInspector, ShowIf("ShowExtraPolationMode"), OnValueChanged("RebindTimeline")]
        public ExtraPolationMode ExtraPolationMode;
        public int StartFrame;
        public int EndFrame;
        public int OtherEaseInFrame;
        public int OtherEaseOutFrame;
        public int SelfEaseInFrame;
        public int SelfEaseOutFrame;
        public int ClipInFrame;

        public int EaseInFrame => OtherEaseInFrame == 0 ? SelfEaseInFrame : OtherEaseInFrame;
        public int EaseOutFrame => OtherEaseOutFrame == 0 ? SelfEaseOutFrame : OtherEaseOutFrame;
        public int Duration => EndFrame - StartFrame;
        public virtual int Length => EndFrame - StartFrame;

        [NonSerialized]
        public Track Track;
        [NonSerialized]
        public TimelineClipPlayable ClipPlayable;

        public Action OnNameChanged;
        public Action OnInspectorRepaint;

        public Clip(Track track, int frame)
        {
            Track = track;
            StartFrame = frame;
            EndFrame = StartFrame + 3;
        }

        public virtual void Init(Track track)
        {
            Track = track;
        }
        public virtual void Bind(TimelineClipPlayable clipPlayable)
        {
            ClipPlayable = clipPlayable;
        }
        public virtual void Unbind()
        {
            ClipPlayable = null;
        }
    }

#if UNITY_EDITOR

    public partial class Timeline
    {
        public float Scale = 1;

        public void AddTrack(Type type)
        {
            Track track = Activator.CreateInstance(type) as Track;
            track.Name = type.Name.Replace("Track", string.Empty);
            m_Tracks.Add(track);
            Init();
        }
        public void RemoveTrack(Track track)
        {
            m_Tracks.Remove(track);
            Init();
        }
        public Clip AddClip(Track track, int frame)
        {
            Clip clip = track.AddClip(frame);

            Init();
            return clip;
        }
        public Clip AddClip(UnityEngine.Object referenceObject, Track track, int frame)
        {
            Clip clip = track.AddClip(referenceObject, frame);

            Init();
            return clip;
        }
        public void RemoveClip(Clip clip)
        {
            clip.Track.RemoveClip(clip);

            Init();
        }
        public void UpdateMix()
        {
            m_Tracks.ForEach(track => track.UpdateMix());
        }
        public void Resort()
        {
            OnValueChanged?.Invoke();
        }

        [UnityEditor.MenuItem("Assets/Create/Taco/Timeline")]
        public static void CreateTimeline()
        {
            Timeline timeline = CreateInstance<Timeline>();
            string path = UnityEditor.AssetDatabase.GetAssetPath(UnityEditor.Selection.activeObject);
            string assetPathAndName = UnityEditor.AssetDatabase.GenerateUniqueAssetPath(path + "/New Timeline.asset");
            UnityEditor.AssetDatabase.CreateAsset(timeline, assetPathAndName);
            UnityEditor.AssetDatabase.SaveAssets();
            UnityEditor.AssetDatabase.Refresh();
            UnityEditor.Selection.activeObject = timeline;
        }
    }

    public abstract partial class Track
    {
        public virtual Type ClipType => typeof(Clip);

        public virtual Clip AddClip(int frame)
        {
            Clip clip = Activator.CreateInstance(ClipType, this, frame) as Clip;
            m_Clips.Add(clip);
            return clip;
        }
        public abstract Clip AddClip(UnityEngine.Object referenceObject, int frame);

        public void RemoveClip(Clip clip)
        {
            m_Clips.Remove(clip);
            UpdateMix();
        }
        public void UpdateMix()
        {
            Clips.ForEach(c => c.UpdateMix());
            OnUpdateMix?.Invoke();
        }
        public Color Color()
        {
            var colorAttributes = GetType().GetCustomAttributes<ColorAttribute>().ToArray();
            return colorAttributes[colorAttributes.Length - 1].Color / 255;
        }
        public virtual bool DragValid()
        {
            return false;
        }

        public void RebindTimeline()
        {
            Timeline.RebindTrack(this);
        }
    }

    [Flags]
    public enum ClipCapabilities
    {
        None = 0,
        Resizable = 0x1,
        Mixable = 0x2,
        ClipInable = 0x4,
    }
    public abstract partial class Clip
    {
        [NonSerialized]
        public bool Invalid;

        public virtual string Name => GetType().Name;
        public virtual bool ShowExtraPolationMode => false;
        public virtual ClipCapabilities Capabilities => ClipCapabilities.None;

        public void UpdateMix()
        {
            OtherEaseInFrame = 0;
            OtherEaseOutFrame = 0;

            if (Invalid)
                return;

            foreach (var clip in Track.Clips)
            {
                if (clip != this && !clip.Invalid)
                {
                    if (clip.StartFrame < StartFrame && clip.EndFrame > EndFrame)
                    {
                        return;
                    }
                    else if (clip.StartFrame > StartFrame && clip.EndFrame < EndFrame)
                    {
                        return;
                    }

                    if (clip.StartFrame < StartFrame && clip.EndFrame > StartFrame)
                    {
                        OtherEaseInFrame = clip.EndFrame - StartFrame;
                    }
                    if (clip.StartFrame > StartFrame && clip.StartFrame < EndFrame)
                    {
                        OtherEaseOutFrame = EndFrame - clip.StartFrame;
                    }
                    if (clip.StartFrame == StartFrame)
                    {
                        if (clip.EndFrame < EndFrame)
                        {
                            OtherEaseInFrame = clip.EndFrame - StartFrame;
                        }
                        else if (clip.EndFrame > EndFrame)
                        {
                            OtherEaseOutFrame = EndFrame - StartFrame;
                        }
                    }
                    SelfEaseInFrame = Mathf.Min(SelfEaseInFrame, Duration - OtherEaseOutFrame);
                    SelfEaseOutFrame = Mathf.Min(SelfEaseOutFrame, Duration - OtherEaseInFrame);
                }
            }
        }
        public bool Contains(float halfFrame)
        {
            return StartFrame < halfFrame && halfFrame < EndFrame;
        }

        public Color Color()
        {
            var colorAttributes = GetType().GetCustomAttributes<ColorAttribute>().ToArray();
            return colorAttributes[colorAttributes.Length - 1].Color / 255;
        }

        public string StartTimeText()
        {
            return $"StartTime:  {(StartFrame / (float)Timeline.FrameRate).ToString("0.00")}S  /  StartFrame:  {StartFrame}F";
        }
        public string EndTimeText()
        {
            return $"EndTime:  {(EndFrame / (float)Timeline.FrameRate).ToString("0.00")}S  /  EndFrame:  {EndFrame}F";
        }
        public string DurationText()
        {
            return $"Duration:  {(Duration / (float)Timeline.FrameRate).ToString("0.00")}S  /  {Duration}F";
        }

        public virtual void RebindTimeline()
        {
            Track.RebindTimeline();
        }
        public virtual void RepaintInspector()
        {
            OnInspectorRepaint?.Invoke();
        }

        public virtual bool IsResizable()
        {
            return (Capabilities & ClipCapabilities.Resizable) == ClipCapabilities.Resizable;
        }
        public virtual bool IsMixable()
        {
            return (Capabilities & ClipCapabilities.Mixable) == ClipCapabilities.Mixable;
        }
        public virtual bool IsClipInable()
        {
            return (Capabilities & ClipCapabilities.ClipInable) == ClipCapabilities.ClipInable;
        }
    }
#endif
}